3c19b87f174ec96e10ad2bf946ad9afa401880e0
3 * Authentication request value object
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 namespace MediaWiki\Auth
;
29 * This is a value object for authentication requests.
31 * An AuthenticationRequest represents a set of form fields that are needed on
32 * and provided from the login, account creation, or password change forms.
37 abstract class AuthenticationRequest
{
39 /** Indicates that the request is not required for authentication to proceed. */
42 /** Indicates that the request is required for authentication to proceed. */
45 /** Indicates that the request is required by a primary authentication
46 * provdier, but other primary authentication providers do not require it. */
47 const PRIMARY_REQUIRED
= 2;
49 /** @var string|null The AuthManager::ACTION_* constant this request was
50 * created to be used for. The *_CONTINUE constants are not used here, the
51 * corresponding "begin" constant is used instead.
53 public $action = null;
55 /** @var int For login, continue, and link actions, one of self::OPTIONAL,
56 * self::REQUIRED, or self::PRIMARY_REQUIRED */
57 public $required = self
::REQUIRED
;
59 /** @var string|null Return-to URL, in case of redirect */
60 public $returnToUrl = null;
62 /** @var string|null Username. May not be used by all subclasses. */
63 public $username = null;
66 * Supply a unique key for deduplication
68 * When the AuthenticationRequests instances returned by the providers are
69 * merged, the value returned here is used for keeping only one copy of
72 * Subclasses should override this if multiple distinct instances would
73 * make sense, i.e. the request class has internal state of some sort.
75 * This value might be exposed to the user in web forms so it should not
76 * contain private information.
80 public function getUniqueId() {
81 return get_called_class();
85 * Fetch input field info
87 * The field info is an associative array mapping field names to info
88 * arrays. The info arrays have the following keys:
89 * - type: (string) Type of input. Types and equivalent HTML widgets are:
90 * - string: <input type="text">
91 * - password: <input type="password">
93 * - checkbox: <input type="checkbox">
94 * - multiselect: More a grid of checkboxes than <select multi>
95 * - button: <input type="image"> if 'image' is set, otherwise <input type="submit">
96 * (uses 'label' as button text)
97 * - hidden: Not visible to the user, but needs to be preserved for the next request
98 * - null: No widget, just display the 'label' message.
99 * - options: (array) Maps option values to Messages for the
100 * 'select' and 'multiselect' types.
101 * - value: (string) Value (for 'null' and 'hidden') or default value (for other types).
102 * - image: (string) URL of an image to use in connection with the input
103 * - label: (Message) Text suitable for a label in an HTML form
104 * - help: (Message) Text suitable as a description of what the field is
105 * - optional: (bool) If set and truthy, the field may be left empty
107 * @return array As above
109 abstract public function getFieldInfo();
112 * Returns metadata about this request.
114 * This is mainly for the benefit of API clients which need more detailed render hints
115 * than what's available through getFieldInfo(). Semantics are unspecified and left to the
116 * individual subclasses, but the contents of the array should be primitive types so that they
117 * can be transformed into JSON or similar formats.
119 * @return array A (possibly nested) array with primitive types
121 public function getMetadata() {
126 * Initialize form submitted form data.
128 * Should always return false if self::getFieldInfo() returns an empty
131 * @param array $data Submitted data as an associative array
132 * @return bool Whether the request data was successfully loaded
134 public function loadFromSubmission( array $data ) {
135 $fields = array_filter( $this->getFieldInfo(), function ( $info ) {
136 return $info['type'] !== 'null';
142 foreach ( $fields as $field => $info ) {
143 // Checkboxes and buttons are special. Depending on the method used
144 // to populate $data, they might be unset meaning false or they
145 // might be boolean. Further, image buttons might submit the
146 // coordinates of the click rather than the expected value.
147 if ( $info['type'] === 'checkbox' ||
$info['type'] === 'button' ) {
148 $this->$field = isset( $data[$field] ) && $data[$field] !== false
149 ||
isset( $data["{$field}_x"] ) && $data["{$field}_x"] !== false;
150 if ( !$this->$field && empty( $info['optional'] ) ) {
156 // Multiselect are too, slightly
157 if ( !isset( $data[$field] ) && $info['type'] === 'multiselect' ) {
161 if ( !isset( $data[$field] ) ) {
164 if ( $data[$field] === '' ||
$data[$field] === [] ) {
165 if ( empty( $info['optional'] ) ) {
169 switch ( $info['type'] ) {
171 if ( !isset( $info['options'][$data[$field]] ) ) {
177 $data[$field] = (array)$data[$field];
178 $allowed = array_keys( $info['options'] );
179 if ( array_diff( $data[$field], $allowed ) !== [] ) {
186 $this->$field = $data[$field];
193 * Describe the credentials represented by this request
195 * This is used on requests returned by
196 * AuthenticationProvider::getAuthenticationRequests() for ACTION_LINK
197 * and ACTION_REMOVE and for requests returned in
198 * AuthenticationResponse::$linkRequest to create useful user interfaces.
200 * @return Message[] with the following keys:
201 * - provider: A Message identifying the service that provides
202 * the credentials, e.g. the name of the third party authentication
204 * - account: A Message identifying the credentials themselves,
205 * e.g. the email address used with the third party authentication
208 public function describeCredentials() {
210 'provider' => new \
RawMessage( '$1', [ get_called_class() ] ),
211 'account' => new \
RawMessage( '$1', [ $this->getUniqueId() ] ),
216 * Update a set of requests with form submit data, discarding ones that fail
217 * @param AuthenticationRequest[] $reqs
219 * @return AuthenticationRequest[]
221 public static function loadRequestsFromSubmission( array $reqs, array $data ) {
222 return array_values( array_filter( $reqs, function ( $req ) use ( $data ) {
223 return $req->loadFromSubmission( $data );
228 * Select a request by class name.
229 * @param AuthenticationRequest[] $reqs
230 * @param string $class Class name
231 * @param bool $allowSubclasses If true, also returns any request that's a subclass of the given
233 * @return AuthenticationRequest|null Returns null if there is not exactly
234 * one matching request.
236 public static function getRequestByClass( array $reqs, $class, $allowSubclasses = false ) {
237 $requests = array_filter( $reqs, function ( $req ) use ( $class, $allowSubclasses ) {
238 if ( $allowSubclasses ) {
239 return is_a( $req, $class, false );
241 return get_class( $req ) === $class;
244 return count( $requests ) === 1 ?
reset( $requests ) : null;
248 * Get the username from the set of requests
250 * Only considers requests that have a "username" field.
252 * @param AuthenticationRequest[] $requests
253 * @return string|null
254 * @throws \UnexpectedValueException If multiple different usernames are present.
256 public static function getUsernameFromRequests( array $reqs ) {
259 foreach ( $reqs as $req ) {
260 $info = $req->getFieldInfo();
261 if ( $info && array_key_exists( 'username', $info ) && $req->username
!== null ) {
262 if ( $username === null ) {
263 $username = $req->username
;
264 $otherClass = get_class( $req );
265 } elseif ( $username !== $req->username
) {
266 $requestClass = get_class( $req );
267 throw new \
UnexpectedValueException( "Conflicting username fields: \"{$req->username}\" from "
268 . "$requestClass::\$username vs. \"$username\" from $otherClass::\$username" );
276 * Merge the output of multiple AuthenticationRequest::getFieldInfo() calls.
277 * @param AuthenticationRequest[] $reqs
279 * @throws \UnexpectedValueException If fields cannot be merged
281 public static function mergeFieldInfo( array $reqs ) {
284 foreach ( $reqs as $req ) {
285 $info = $req->getFieldInfo();
290 foreach ( $info as $name => $options ) {
291 if ( $req->required
!== self
::REQUIRED
) {
292 // If the request isn't required, its fields aren't required either.
293 $options['optional'] = true;
295 $options['optional'] = !empty( $options['optional'] );
298 if ( !array_key_exists( $name, $merged ) ) {
299 $merged[$name] = $options;
300 } elseif ( $merged[$name]['type'] !== $options['type'] ) {
301 throw new \
UnexpectedValueException( "Field type conflict for \"$name\", " .
302 "\"{$merged[$name]['type']}\" vs \"{$options['type']}\""
305 if ( isset( $options['options'] ) ) {
306 if ( isset( $merged[$name]['options'] ) ) {
307 $merged[$name]['options'] +
= $options['options'];
309 // @codeCoverageIgnoreStart
310 $merged[$name]['options'] = $options['options'];
311 // @codeCoverageIgnoreEnd
315 $merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
317 // No way to merge 'value', 'image', 'help', or 'label', so just use
318 // the value from the first request.
327 * Implementing this mainly for use from the unit tests.
329 * @return AuthenticationRequest
331 public static function __set_state( $data ) {
333 foreach ( $data as $k => $v ) {